﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace aboveXWords
{
  internal class Words
  {
    private readonly HashSet<string> m_words = new HashSet<string>();

    #region Load dictionary

    public int Load()
    {
      string rootPath = Directory.GetCurrentDirectory();
      const string source = @"dsso-1.44.txt";
      return Load(Path.Combine(rootPath, source));
    }

    public int Load(string path)
    {
      using (var reader = new BufferedStream(File.OpenRead(path), 4096))
      using (var sreader = new StreamReader(reader, Encoding.UTF8))
      {
        string line;
        while (!String.IsNullOrEmpty(line = NextLine(sreader)))
        {
          // Check if we're at the start of a record.
          // It should start with a digit, actually 6 of'em?
          // Anyway, they end with the letter 'r'
          int index = line.IndexOf('r');
          if (index == -1) continue;
          // We're at the start of a new record - skip the revision #
          index = line.IndexOf('<', index);
          if (index == -1) continue;
          ++index;
          int end = line.IndexOf('>', index);
          if (end == -1) continue;

          // Parse the word list, each word separated by a ':'
          while ((index = line.IndexOf(':', ++end)) != -1)
          {
            AddWord(line.Substring(end, index - end));
            end = index;
          }
          AddWord(line.Substring(end));
        }
      }
      //using (var fs = new BufferedStream(new FileStream(Path.ChangeExtension(path,"doc"), FileMode.Create), 4096))
      //using (var sw = new StreamWriter(fs, Encoding.UTF8))
      //  foreach (string key in m_words)
      //    sw.WriteLine(key);
      return m_words.Count;
    }

    /// <summary>
    /// Add a "new" word to the dictionary.
    /// If the word contains a ' then there's an
    /// alternative spelling of the word, split the
    /// string and add each found word.
    /// </summary>
    /// <param name="word"></param>
    private void AddWord(string word)
    {
      if (String.IsNullOrEmpty(word) || word == "!") return;
      foreach (string w in word.Split(new[] {','}).Where(w => !String.IsNullOrEmpty(w.Trim()) && w.Trim() != "!"))
        m_words.Add(w.Trim().ToLowerInvariant());
    }

    /// <summary>
    /// Get next un-commented, non-empty line from the file
    /// </summary>
    /// <param name="reader"></param>
    /// <returns></returns>
    private static string NextLine(StreamReader reader)
    {
      string line = String.Empty;
      while (!reader.EndOfStream)
      {
        line = reader.ReadLine().Trim();
        // Skip empty lines
        if (line.Length == 0) continue;
        // Skip comments
        if (line[0] != '#') break;
      }
      return line;
    }

    #endregion

    #region Search dictionary

    /// <summary>
    /// Pattern search
    /// * = 0 or more characters
    /// ? = 1 character
    /// </summary>
    /// <param name="pattern"></param>
    /// <returns></returns>
    public List<string> Find(string pattern)
    {
      string regexp = "^" + pattern.ToLowerInvariant().Replace("*", @".*").Replace("?", @".") + "$";
      return m_words.Where(word => Regex.Match(word, regexp).Success).ToList();
    }

    /// <summary>
    /// Pattern match
    /// pattern is a commanseparated string of numbers and/or characters.
    /// e.g. 12,21,32,12,a,a,23,b
    /// </summary>
    /// <param name="match"></param>
    /// <returns></returns>
    public List<string> Match(string match)
    {
      // First constrain is the given characters.
      string[] matchs = match.Split(new[] {','});
      string regexp = "^";
      int index;
      for (int i = 0; i < matchs.Length; ++i)
      {
        matchs[i] = matchs[i].Trim();
        if (Int32.TryParse(matchs[i], out index))
        {
          regexp += @".";
          matchs[i] = "100" + matchs[i];
        }
        else
          regexp += matchs[i];
      }
      regexp += "$";

      int order = 0;
      foreach (string pattern in matchs.Where(t => Int32.TryParse(t, out index) && index > 100))
      {
        ++order;
        for (int x = 0; x < matchs.Length; ++x)
          if (matchs[x] == pattern)
            matchs[x] = order.ToString();
      }

      var matches = new List<string>();
      foreach (string word in m_words.Where(word => Regex.Match(word, regexp).Success))
      {
        order = 0;
        bool itIsAMatch = true;
        var m = new string[word.Length];
        for (int i = 0; i < word.Length; ++i)
        {
          if (!String.IsNullOrEmpty(m[i]) || regexp[i + 1] != '.') continue;
          ++order;
          for (int x = 0; x < word.Length; ++x)
            if (word[x] == word[i])
            {
              m[x] = order.ToString();
              // Early check if possible match
              if (m[x] != matchs[x])
              {
                itIsAMatch = false;
                break;
              }
            }
        }
        if (itIsAMatch)
          matches.Add(word);
      }
      return matches;
    }

    #endregion

    #region Web code

    public int Read()
    {
      var ord = new HashSet<string>();
      var currOrd = new List<char>();

      string rootPath = Directory.GetCurrentDirectory();
      const string source = @"dsso-1.44.txt";
      const string dest = @"ordlista.txt";

      using (var reader = new BufferedStream(File.OpenRead(Path.Combine(rootPath, source)), 4096))
      using (var sreader = new StreamReader(reader, Encoding.UTF8))
      {
        bool reading = false,
             hasRead = false;
        while (!sreader.EndOfStream)
        {
          var pos = (char) sreader.Read();
          switch (pos)
          {
            case '>':
              reading = true;
              break;
            case ':':
              AddOrd(ord, currOrd);
              break;
            case '\r':
            case '\n':
              if (hasRead) // ibland finns orden på raden under '>'-tecknet.
              {
                hasRead = reading = false;
                // slut på lista av böjningar
                AddOrd(ord, currOrd);
              }
              break;
            default:
              if (reading && pos != ':')
              {
                // läser fortfarande och vill inte lägga till ':'.

                if (pos == '<') // '<' förekommer aldrig i ord, så skippa det här ordet
                {
                  currOrd.Clear();
                  break;
                }

                // annars lägg till ordet.
                currOrd.Add(pos);
                hasRead = true;
              }
              break;
          }
        }
      }

      Debug.WriteLine(string.Format("Finished collecting words ({0} of them)! Writing them!",
                                    ord.Count));

      int c = 0;

      using (var fs = new BufferedStream(new FileStream(rootPath + dest, FileMode.Create), 4096))
      using (var sw = new StreamWriter(fs, Encoding.UTF8))
        foreach (string ordet in ord)
        {
          c++;
          sw.WriteLine(ordet);
          if (c%1000 == 0)
            Debug.WriteLine(string.Format("Written {0} words. Current word: {1}",
                                          c, ordet));
        }
      return c;
    }

    private static void AddOrd(HashSet<string> ord, List<char> currOrd)
    {
      if (currOrd.Count == 0) return;
      ord.Add(new string(currOrd.ToArray()));
      currOrd.Clear();
    }

    #endregion
  }
}